package com.indi.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.indi.common.to.es.SkuEsModelTO;
import com.indi.common.utils.R;
import com.indi.gulimall.search.config.ElasticsearchConfig;
import com.indi.gulimall.search.constant.EsConstant;
import com.indi.gulimall.search.feign.ProductFeignService;
import com.indi.gulimall.search.service.MallSearchService;
import com.indi.gulimall.search.vo.*;
import com.indi.gulimall.search.vo.SearchResultVO.AttrVO;
import com.indi.gulimall.search.vo.SearchResultVO.NavVO;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Resource
    private ProductFeignService productFeignService;


    @Override
    public SearchResultVO search(SearchParamVO param) {
        // 构建检索请求
        SearchRequest searchRequest = buildSearchRequest(param);
        SearchResultVO result = null;
        try {
            // 检索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);

            // 处理返回结果
            result = buildSearchResult(searchResponse, param);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 拼装DSL
     *
     * @param param
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParamVO param) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // must 标题
        if (StringUtils.isNotEmpty(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
        }

        // filter 分类
        if (StringUtils.isNotEmpty(param.getCategory3Id())) {
            boolQuery.filter(QueryBuilders.termQuery("categoryId", param.getCategory3Id()));
        }

        // 品牌
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }

        // 有没有货，查询条件为空则忽略这个条件
        if (param.getStockOrNot() != null) {
            boolQuery.filter(QueryBuilders.termQuery("stockOrNot", param.getStockOrNot() == 1));
        }

        // 属性
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            for (String attr : param.getAttrs()) {
                String[] s = attr.split("_");
                String[] attrValues = s[1].split(":");
                BoolQueryBuilder attrBoolQuery = QueryBuilders.boolQuery();
                attrBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", s[0]));
                attrBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                NestedQueryBuilder attrsNested = QueryBuilders.nestedQuery("attrs", attrBoolQuery, ScoreMode.None);
                boolQuery.filter(attrsNested);
            }
        }

        // 价格区间
        if (StringUtils.isNotEmpty(param.getSkuPrice()) && param.getSkuPrice().length() > 1) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            if (param.getSkuPrice().startsWith("_")) {
                Float num = Float.parseFloat(param.getSkuPrice().substring(1));
                rangeQuery.lte(num);
            } else if (param.getSkuPrice().endsWith("_")) {
                Float num = Float.parseFloat(param.getSkuPrice().substring(0, param.getSkuPrice().length() - 1));
                rangeQuery.gte(num);
            } else {
                String[] s = param.getSkuPrice().split("_");
                rangeQuery.gte(Float.parseFloat(s[0])).lte(Float.parseFloat(s[1]));
            }

            boolQuery.filter(rangeQuery);
        }

        sourceBuilder.query(boolQuery);

        // 排序
        if (StringUtils.isNotEmpty(param.getSort())) {
            String[] s = param.getSort().split("_");
            sourceBuilder.sort(s[0], s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
        }

        // 分页
        sourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGE_SIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGE_SIZE);

        // highlight
        if (StringUtils.isNotEmpty(param.getKeyword())) {
            HighlightBuilder highlight = new HighlightBuilder();
            highlight.field("skuTitle").preTags("<b style = 'color:red'>").postTags("</b>");
            sourceBuilder.highlighter(highlight);
        }

        // agg 品牌 50 1 1
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(50);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        sourceBuilder.aggregation(brandAgg);

        // 分类 20 1
        TermsAggregationBuilder categoryAgg = AggregationBuilders.terms("category_agg").field("categoryId").size(20);
        categoryAgg.subAggregation(AggregationBuilders.terms("category_name_agg").field("categoryName").size(1));
        sourceBuilder.aggregation(categoryAgg);

        // 属性 1 10
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        attrAgg.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(attrAgg);
//        System.out.println("最终DSL: " + sourceBuilder.toString());
        SearchRequest searchRequest = new SearchRequest(EsConstant.PRODUCT_INDEX).source(sourceBuilder);
        return searchRequest;
    }

    /**
     * 将es的数据转换为我们需要的格式
     *
     * @param searchResponse
     * @param param
     * @return
     */
    private SearchResultVO buildSearchResult(SearchResponse searchResponse, SearchParamVO param) {
        SearchHits hits = searchResponse.getHits();
        SearchResultVO result = new SearchResultVO();

        // 商品数据
        SearchHit[] subHits = hits.getHits();
        List<SkuEsModelTO> products = new ArrayList<>();
        if (subHits != null && subHits.length > 0) {
            for (SearchHit subHit : subHits) {
                String source = subHit.getSourceAsString();
                SkuEsModelTO skuEsModelTO = JSON.parseObject(source, SkuEsModelTO.class);
                if (StringUtils.isNotEmpty(param.getKeyword())) {
                    HighlightField skuTitle = subHit.getHighlightFields().get("skuTitle");
                    skuEsModelTO.setSkuTitle(skuTitle.getFragments()[0].string());
                }
                products.add(skuEsModelTO);
            }
        }
        result.setProducts(products);

        // 属性聚合
        ParsedNested attrAgg = searchResponse.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        List<AttrVO> attrVOs = new ArrayList<>();
        for (Bucket bucket : attrIdAgg.getBuckets()) {
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            AttrVO attrVO = new AttrVO();
            long attrId = bucket.getKeyAsNumber().longValue();
            attrVO.setAttrId(attrId);
            attrVO.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString())
                    .collect(Collectors.toList());
            attrVO.setAttrValue(attrValues);


            attrVOs.add(attrVO);
        }
        result.setAttrVOs(attrVOs);

        // 分类聚合
        ParsedTerms categoryAgg = searchResponse.getAggregations().get("category_agg");
        List<SearchResultVO.CategoryVO> categoryVOs = new ArrayList<>();
        for (Bucket bucket : categoryAgg.getBuckets()) {
            ParsedStringTerms categoryNameAgg = bucket.getAggregations().get("category_name_agg");
            SearchResultVO.CategoryVO categoryVO = new SearchResultVO.CategoryVO();
            categoryVO.setCategoryId(bucket.getKeyAsNumber().longValue());
            categoryVO.setCategoryName(categoryNameAgg.getBuckets().get(0).getKeyAsString());
            categoryVOs.add(categoryVO);
        }
        result.setCategoryVOs(categoryVOs);

        // 品牌聚合
        ParsedTerms brandAgg = searchResponse.getAggregations().get("brand_agg");
        List<SearchResultVO.BrandVO> brandVOs = new ArrayList<>();
        for (Bucket bucket : brandAgg.getBuckets()) {
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            SearchResultVO.BrandVO brandVO = new SearchResultVO.BrandVO();
            brandVO.setBrandId(bucket.getKeyAsNumber().longValue());
            brandVO.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());
            brandVO.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());
            brandVOs.add(brandVO);
        }
        result.setBrandVOs(brandVOs);

        // 当前页码
        result.setPageNum(param.getPageNum());

        // 总记录数
        long totalHits = hits.getTotalHits().value;
        result.setTotal(totalHits);

        // 总页码
        int totalPages = (int) (totalHits % EsConstant.PRODUCT_PAGE_SIZE == 0
                ? totalHits / EsConstant.PRODUCT_PAGE_SIZE
                : totalHits / EsConstant.PRODUCT_PAGE_SIZE + 1);
        result.setTotalPages(totalPages);

        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        // 属性的面包屑导航
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            List<NavVO> navVOs = param.getAttrs().stream().map(attr -> {
                NavVO navVO = new NavVO();
                String[] s = attr.split("_");
                navVO.setNavValue(s[1]);
                R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                result.getAttrIds().add(Long.parseLong(s[0]));
                if (r.getCode() == 0) {
                    AttrResponseVO data = r.getData("attr", new TypeReference<AttrResponseVO>() {
                    });
                    navVO.setNavName(data.getAttrName());
                } else {
                    navVO.setNavName(s[0]);
                }

                String linkUrl = replaceQueryString(param, attr, "attrs");
                navVO.setLink(linkUrl);
                return navVO;
            }).collect(Collectors.toList());
            result.setNavVOs(navVOs);
        }

        // 品牌、分类的面包屑导航
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            List<NavVO> navVOs = result.getNavVOs();
            NavVO navVO = new NavVO();
            navVO.setNavName("品牌");

            // 远程查询所有品牌
            R r = productFeignService.brandListByIds(param.getBrandId());
            if (r.getCode() == 0) {
                List<BrandVO> brands = r.getData("brands", new TypeReference<List<BrandVO>>() {
                });

                StringBuffer buffer = new StringBuffer();
                String linkUrl = "";
                for (BrandVO brandVO : brands) {
                    buffer.append(brandVO.getName() + ";");
                    linkUrl = replaceQueryString(param, brandVO.getBrandId() + "", "brandId");
                }
                navVO.setNavValue(buffer.toString());
                navVO.setLink(linkUrl);

            }
            navVOs.add(navVO);
        }

        if (StringUtils.isNotEmpty(param.getCategory3Id())) {
            List<NavVO> navVOs = result.getNavVOs();
            NavVO navVO = new NavVO();
            navVO.setNavName("分类");

            // TODO 远程查询所有分类
            R r = productFeignService.categoryInfo(Long.parseLong(param.getCategory3Id()));
            if (r.getCode() == 0) {
                CategoryVO categoryVO = r.getData(new TypeReference<CategoryVO>() {
                });
                navVO.setNavValue(categoryVO.getName());
                String linkUrl = replaceQueryString(param, categoryVO.getCatId() + "", "category3Id");
                navVO.setLink(linkUrl);
            }
            navVOs.add(navVO);
        }

        return result;
    }

    /**
     * 提取成通用方法
     *
     * @param param 参数列表
     * @param value 要替换的值
     * @param key   要替换的url字段
     * @return
     */
    private String replaceQueryString(SearchParamVO param, String value, String key) {
        String encode = null;
        //  fix: 上来直接选择属性，以? 开头的参数会导致，跳转链接错误
        try {
            encode = URLEncoder.encode(value, "UTF-8");
            encode = encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        String queryString = param.getQueryString();
        String linkUrl = "";
        if (param.getQueryString().startsWith("&")) {
            // 这种情况说明是全&拼接的，直接替换
            String link = param.getQueryString().replace("&" + key + "=" + encode, "");
            linkUrl = "http://search.gulimall.com/list.html?" + link;
        } else {
            if (queryString.indexOf("&") != -1) {
                // 这说明第1个参数是?拼的，后面有好几个参数
                // 截取出第一个参数来，单独处理
                String attr0 = queryString.substring(0, queryString.indexOf("&"));
                String attrCompare = key + "=" + encode;
                // 如果当前设置的属性是第一个参数
                if (attrCompare.equals(attr0)) {
                    String link = queryString.replace(attr0, "");
                    linkUrl = "http://search.gulimall.com/list.html?" + link;

                } else {
                    String link = param.getQueryString().replace("&" + key + "=" + encode, "");
                    linkUrl = "http://search.gulimall.com/list.html?" + link;
                }
            } else {
                // 这说明是只有1个参数，并且是?拼的，直接替换
                String link = param.getQueryString().replace(key + "=" + encode, "");
                linkUrl = "http://search.gulimall.com/list.html" + link;
            }
        }
        return linkUrl;
    }
}
